package com.rizhov.graphmodels;

import java.util.Random;

public class Matrix implements Cloneable {

    public static void main(String[] args) {
	Matrix matrix = new Matrix(3, 3);

	Random r = new Random();
	for (int i = 0; i < 3; i++) {
	    for (int j = 0; j < 3; j++) {
		matrix.setCell(i, j, r.nextInt(100));
	    }
	}

	// getMinor test
	// System.out.println("Matrix view:");
	// System.out.println(matrix.toString());
	//
	// System.out.println("getMinor(1, 1):");
	// matrix = matrix.getMinor(1, 1);
	// System.out.println(matrix.toString());
	//
	// System.out.println("getMinor(0, 0):");
	// matrix = matrix.getMinor(0, 0);
	// System.out.println(matrix.toString());
	//
	// System.out.println("getMinor(0, 2):");
	// matrix = matrix.getMinor(0, 0);
	// System.out.println(matrix.toString());
	//
	// System.out.println("Add two rows, two columns and fill with random values:");
	// matrix.addRow(0);
	// matrix.addColumn(0);
	// matrix.addRow(0);
	// matrix.addColumn(0);
	// matrix.fillMatrixWithRandomValues();
	// System.out.println(matrix.toString());
	//
	// System.out.println("getMinor(1, 1):");
	// matrix = matrix.getMinor(1, 1);
	// System.out.println(matrix.toString());
	//
	// System.out.println("getMinor(0, 0):");
	// matrix = matrix.getMinor(0, 0);
	// System.out.println(matrix.toString());

	// removeColumn and addColumn test
	// System.out.println("Matrix view:");
	// System.out.println(matrix.toString());
	//
	// System.out.println("Remove first column:");
	// matrix.removeColumn(0);
	// System.out.println(matrix.toString());
	//
	// System.out.println("Remove matrix's last column:");
	// matrix.removeColumn(matrix.getColumnCount() - 1);
	// System.out.println(matrix.toString());
	//
	// System.out.println("Remove matrix's last column:");
	// matrix.removeColumn(matrix.getColumnCount() - 1);
	// System.out.println(matrix.toString());
	//
	// System.out.println("Add first column:");
	// matrix.addColumn(0);
	// System.out.println(matrix.toString());
	//
	// System.out.println("Fill matrix with random values:");
	// matrix.fillMatrixWithRandomValues();
	// System.out.println(matrix.toString());
	//
	// System.out.println("Add last column:");
	// matrix.addColumn(matrix.getColumnCount());
	// System.out.println(matrix.toString());
	//
	// System.out.println("Fill matrix with random values:");
	// matrix.fillMatrixWithRandomValues();
	// System.out.println(matrix.toString());
	//
	System.out.println("Add second column");
	matrix.addColumn(1);
	System.out.println(matrix.toString());

	System.out.println("Remove second column");
	matrix.removeColumn(1);
	System.out.println(matrix.toString());

	// removeRow and addRow
	System.out.println("Matrix view:");
	System.out.println(matrix.toString());

	System.out.println("Remove first row:");
	matrix.removeRow(0);
	System.out.println(matrix.toString());

	// System.out.println("Remove matrix's last row:");
	// matrix.removeRow(matrix.getRowCount() - 1);
	// System.out.println(matrix.toString());
	//
	// System.out.println("Remove matrix's last row:");
	// matrix.removeRow(matrix.getRowCount() - 1);
	// System.out.println(matrix.toString());
	//
	// System.out.println("Add first row:");
	// matrix.addRow(0);
	// System.out.println(matrix.toString());
	//
	// System.out.println("Fill matrix with random values:");
	// matrix.fillMatrixWithRandomValues();
	// System.out.println(matrix.toString());
	//
	// System.out.println("Add last row:");
	// matrix.addRow(matrix.getRowCount());
	// System.out.println(matrix.toString());
	//
	// System.out.println("Fill matrix with random values:");
	// matrix.fillMatrixWithRandomValues();
	// System.out.println(matrix.toString());
	//
	// System.out.println("Add second row");
	// matrix.addRow(1);
	// System.out.println(matrix.toString());
	//
	// System.out.println("Remove second row");
	// matrix.removeRow(1);
	// System.out.println(matrix.toString());
    }
    private Object[][] matrix = null;
    private int rowCount = 0;

    private int columnCount = 0;

    // null is filling of empty cell
    private static final Object EMPTY_CELL = null;

    public Matrix() {

    }

    public Matrix(int rowCount, int columnCount) {
	this.rowCount = rowCount;
	this.columnCount = columnCount;
	if (columnCount == 0 || rowCount == 0) {
	    columnCount = 0;
	    rowCount = 0;
	}
	matrix = new Object[rowCount][columnCount];
	for (int i = 0; i < rowCount; i++) {
	    for (int j = 0; j < columnCount; j++) {
		matrix[i][j] = EMPTY_CELL;
	    }
	}
    }

    public boolean addColumn(int columnIndex) {
	if (columnCount == 0 && rowCount == 0) {
	    rowCount = 1;
	}
	if (columnIndex >= 0 && columnIndex <= columnCount) {
	    Object[][] newMatrix = new Object[rowCount][columnCount + 1];

	    for (int i = 0; i < rowCount; i++) {
		newMatrix[i][columnIndex] = EMPTY_CELL;
	    }

	    for (int i = 0; i < rowCount; i++) {
		for (int j = 0; j < columnCount; j++) {
		    if (j >= columnIndex) {
			newMatrix[i][j + 1] = matrix[i][j];
		    } else {
			newMatrix[i][j] = matrix[i][j];
		    }
		}
	    }

	    columnCount++;
	    this.matrix = newMatrix;

	    return true;
	} else {
	    return false;
	}

    }

    public boolean addRow(int rowIndex) {
	if (columnCount == 0 && rowCount == 0) {
	    columnCount = 1;
	}
	if (rowIndex >= 0 && rowIndex <= rowCount) {
	    Object[][] newMatrix = new Object[rowCount + 1][];

	    // creating of new clear row for filling
	    Object[] clearRow = new Object[columnCount];
	    for (int i = 0; i < columnCount; i++) {
		clearRow[i] = EMPTY_CELL;
	    }

	    // fill new matrix
	    for (int i = 0; i < rowCount + 1; i++) {
		if (i == rowIndex) {
		    newMatrix[i] = clearRow;
		} else if (i > rowIndex) {
		    newMatrix[i] = matrix[i - 1];
		} else {
		    newMatrix[i] = matrix[i];
		}
	    }

	    this.matrix = newMatrix;
	    rowCount++;

	    return true;
	} else {
	    // rowIndex is out of matrix range
	    return false;
	}
    }

    public void clearCell(int rowIndex, int columnIndex) {
	if (rowIndex >= 0 && rowIndex < rowCount && columnIndex >= 0 && columnIndex < columnCount) {
	    matrix[rowIndex][columnIndex] = EMPTY_CELL;
	}
    }

    @Override
    protected Matrix clone() {
	Matrix clone = new Matrix();

	clone.matrix = new Object[rowCount][columnCount];
	for (int i = 0; i < rowCount; i++) {
	    for (int j = 0; j < columnCount; j++) {
		clone.matrix[i][j] = matrix[i][j];
	    }
	}

	clone.rowCount = this.rowCount;
	clone.columnCount = this.columnCount;

	return clone;
    }

    @Override
    public boolean equals(Object obj) {
	if (((Matrix) obj).rowCount != this.rowCount || ((Matrix) obj).columnCount != this.columnCount) {
	    return false;
	}
	for (int i = 0; i < rowCount; i++) {
	    for (int j = 0; j < columnCount; j++) {
		if (((Matrix) obj).matrix[i][j] != matrix[i][j]) {
		    return false;
		}
	    }
	}
	return true;
    }

    @SuppressWarnings("unused")
    private void fillMatrixWithRandomValues() {
	Random r = new Random();
	for (int i = 0; i < rowCount; i++) {
	    for (int j = 0; j < columnCount; j++) {
		matrix[i][j] = r.nextInt(100);
	    }
	}
    }

    public Object getCell(int rowIndex, int columnIndex) {
	if (rowIndex >= 0 && rowIndex < rowCount && columnIndex >= 0 && columnIndex <= columnCount) {
	    return matrix[rowIndex][columnIndex];
	} else {
	    return null;
	}
    }

    public int getColumnCount() {
	return columnCount;
    }

    public Matrix getMinor(int rowIndex, int columnIndex) {
	if (rowIndex >= 0 && rowIndex < rowCount && columnIndex >= 0 && columnIndex < columnCount) {
	    Matrix minorMatrix = this.clone();
	    minorMatrix.removeColumn(columnIndex);
	    minorMatrix.removeRow(rowIndex);

	    return minorMatrix;
	} else {
	    return null;
	}
    }

    public int getRowCount() {
	return rowCount;
    }

    public boolean isCellFull(int rowIndex, int columnIndex) {
	if (rowIndex >= 0 && rowIndex < rowCount && columnIndex >= 0 && columnIndex < columnCount) {
	    if (matrix[rowIndex][columnIndex] == EMPTY_CELL) {
		return false;
	    } else {
		return true;
	    }
	} else {
	    return false;
	}
    }

    public boolean removeColumn(int columnIndex) {
	if (columnIndex >= 0 && columnIndex < columnCount) {
	    Object[][] newMatrix = new Object[rowCount][columnCount - 1];

	    for (int i = 0; i < rowCount; i++) {
		for (int j = 0; j < columnCount - 1; j++) {
		    if (j >= columnIndex) {
			newMatrix[i][j] = matrix[i][j + 1];
		    } else {
			newMatrix[i][j] = matrix[i][j];
		    }
		}
	    }
	    columnCount--;
	    if (columnCount == 0) {
		rowCount = 0;
	    }
	    this.matrix = newMatrix;

	    return true;
	} else {
	    return false;
	}
    }

    public boolean removeRow(int rowIndex) {
	if (rowIndex >= 0 && rowIndex < this.rowCount) {
	    Object[][] newMatrix = new Object[rowCount - 1][];
	    for (int i = 0; i < rowCount - 1; i++) {
		if (i >= rowIndex) {
		    newMatrix[i] = matrix[i + 1];
		} else {
		    newMatrix[i] = matrix[i];
		}
	    }
	    rowCount--;
	    if (rowCount == 0) {
		columnCount = 0;
	    }
	    this.matrix = newMatrix;

	    return true;
	} else {
	    return false;
	}
    }

    public void setCell(int rowIndex, int columnIndex, Object cellData) {
	if (rowIndex >= 0 && rowIndex < rowCount && columnIndex >= 0 && columnIndex < columnCount) {
	    matrix[rowIndex][columnIndex] = cellData;
	}
    }

    @Override
    public String toString() {
	StringBuilder sb = new StringBuilder();
	for (int i = 0; i < rowCount; i++) {
	    for (int j = 0; j < columnCount; j++) {
		if (matrix[i][j] == EMPTY_CELL) {
		    sb.append("[   free   ]");
		} else {
		    sb.append(String.format("[%10s]", matrix[i][j].toString()));
		}
	    }
	    sb.append("\n");
	}
	return sb.toString();
    }
}
